/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "TextureSimple.h"
#include <lib3d/hard/Hardware.h>
#include <xml/Stream.h>
#include <Color.h>
#include <lib3d/hard/Texture.h>
#include <safe_new.h>
#include <boost/bind.hpp>

namespace liba {
namespace lib3d {
namespace material {

bool TextureSimpleName::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if( name == "Image" )
		return converter::convert( value, &image );
	if( name == "Wrap" )
		return converter::convert( value, &wrap );
	return false;
}

bool TextureSimpleName::operator<( const TextureSimpleName & na )const
{
	return image < na.image;
}

void TextureSimpleRep::load(const TextureSimpleName & name)
{
	if( !pl )
		pl.reset(new pictureformat::Loader(name.image));
	if( !pl->is_ready() )
		return;
	if( !pl->is_valid() )
		THROW(GluckBadResource( __FUNCTION__, "pl is not valid", name.image ));
	alpha_channel = pictureformat::GetAlphaBits( pl->get_best_format() ) != 0;
	this->width = pl->get_width();
	this->height = pl->get_height();
	HardVector::iterator it = hard_vector.begin();
	for( ;it != hard_vector.end(); ++it )
	{
		if(!it->first->is_operational())
			continue;

		int mipmap_levels = material::Texture::get_mipmap_levels( pl.get() );
		if( !it->second )		
			it->second = it->first->create_texture( mipmap_levels, pl->get_best_format(), pl->get_width(), pl->get_height() );

		it->second->set_wrap(name.wrap);

		if( !it->second->is_valid() )
		{
			// can be created less mipmaps, then requested
			for( int m = 0; m != it->second->get_mipmap_levels(); ++m )
			{
				int stride = 0;
				void * dst = it->second->lock(m, &stride);
				if( !dst )
					continue;
				pl->reset( it->second->get_hardware_format(), m );
				// Load data from pl
				for( int i = 0; i != pl->get_height(m); ++i )
				{
					pl->unpack_scanline(dst);
					dst = reinterpret_cast<char *>(dst) + stride;
				}
				it->second->unlock(m);
			}
		}
	}
	pl.reset();
	stop_loading();
}

int TextureSimpleRep::get_width(bool forceLoad)
{
	if (pl && !pl->is_ready())
	{
		if (!forceLoad)
			return 0;

		start_loading();
		return 0;
	}

	return this->width;
}

int TextureSimpleRep::get_height(bool forceLoad)
{
	if (pl && !pl->is_ready())
	{
		if (!forceLoad)
			return 0;

		start_loading();
		return 0;
	}
	
	return this->height;
}

//only RGBA supported
void TextureSimpleRep::set_raw_data(void * data, int width, int height)
{
	pictureformat::Format format = pictureformat::R8G8B8A8;
	alpha_channel = pictureformat::GetAlphaBits(format) != 0;
	HardVector::iterator it = hard_vector.begin();
	for( ;it != hard_vector.end(); ++it )
	{
		if(!it->first->is_operational())
			continue;
		
		if( it->second )
		{
			delete it->second; it->second = 0;
		}
		
		it->second = it->first->create_texture(0, format, width, height);
		if( !it->second->is_valid() )
		{
			int stride = 0;
			void * dst = it->second->lock(0, &stride);
			if( !dst )
				continue;
			
			void * src = data;
			
			for( int i = 0; i != height; ++i )
			{
				memcpy(dst, src, width * 4);
				dst = reinterpret_cast<char *>(dst) + stride;
				src = reinterpret_cast<char *>(src) + width * 4;
			}
			it->second->unlock(0);
		}
	}
}
	
//only RGBA supported
void TextureSimple::set_raw_data(void * data, int width, int height)
{
	if(!data)
		return;
	
	if(get_rep())
		get_rep()->set_raw_data(data, width, height);
}
	
void TextureSimpleRep::render_to_list( hard::Hardware * hardware, hard::RenderTexture * render_texture )
{
	hard::Texture * tex = find_data( hardware );
	if( !tex || !tex->is_valid() )
	{
		start_loading();
		return;
	}
	render_texture->texture = tex;
	render_texture->alpha_channel = alpha_channel;
}

void TextureSimpleRep::on_add_hardware(hard::Manager::hardware_type * ha)
{
	hard_vector.push_back( SweetPair(ha, (liba::lib3d::hard::Texture*)0) );
	start_loading();
}

TextureSimple::TextureSimple(const Atom & animate_node_name, const TextureSimpleName & name)
	: Texture(animate_node_name, name.wrap)
{
	this->RegisterProperty(image_name, &image);
	image.Changed().connect(boost::bind(&TextureSimple::imageChanged, this, _1));
	load_rep(name);
}

TextureSimple::TextureSimple(hard::Hardware * hardware, hard::Texture * texture)
	: Texture(Atom(""), false)
{
	this->RegisterProperty(image_name, &image);
	image.Changed().connect(boost::bind(&TextureSimple::imageChanged, this, _1));
	load_rep(TextureSimpleName(hardware, texture));
}

TextureSimple::TextureSimple(const TextureSimple& rhs)
	: Texture(rhs)
	, SharedPrecached<TextureSimpleName, TextureSimpleRep>(rhs)
{
	this->RegisterProperty(image_name, &image);
	image.Changed().connect(boost::bind(&TextureSimple::imageChanged, this, _1));
}

void TextureSimple::imageChanged(const filesystem::String& newValue)
{
	TextureSimpleName oldName = get_rep_name();
	
	if (oldName.image != newValue)
	{
		oldName.image = newValue;
		unload_rep();
		load_rep(oldName);
	}
}

TextureSimple * TextureSimple::clone()const
{
	return new TextureSimple(*this);
}

} // namespace material
} // namespace lib3d
} // namespace liba

